﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 版权所有 (c) 2022 北京超维景生物科技有限公司 保留所有权利。
 * 
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/2/22 18:17:12
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using ImageK.Java;
using ImageK.Measure;
using ImageK.Plugin;
using ImageK.Plugin.Filter;
using ImageK.Plugin.Frame;
using ImageK.Process;
using ImageK.Util;

namespace ImageK.Gui
{
	/** This class represents a collection of points that can be associated 
	 * with counters. Use the getPolygon() or getFloatPolygon() methods
	 * to retrieve the coordinates of the points. 
	 * @see <a href="http://wsr.imagej.net/macros/js/PointProperties.js">PointProperties.js</a>
    */
	public class PointRoi : PolygonRoi
	{
	    public static string[] sizes = new string[] {"Tiny", "Small", "Medium", "Large", "Extra Large", "XXL", "XXXL"};
        public static string[] types = new string[] { "Hybrid", "Cross", "Dot", "Circle"};
        public const int HYBRID = 0, CROSS = 1, CROSSHAIR = 1, DOT = 2, CIRCLE = 3;
        private const string TYPE_KEY = "point.type";
        private const string SIZE_KEY = "point.size";
        private const string CROSS_COLOR_KEY = "point.cross.color";
        private const int TINY = 1, SMALL = 3, MEDIUM = 5, LARGE = 7, EXTRA_LARGE = 11, XXL = 17, XXXL = 25;
        private BasicStroke twoPixelsWide = new BasicStroke(2);
        private BasicStroke threePixelsWide = new BasicStroke(3);
        private BasicStroke fivePixelsWide = new BasicStroke(5);
        private static int defaultType = HYBRID;
        private static int defaultSize = SMALL;
        private static Font font;
        private static Color defaultCrossColor = Color.White;
        private int fontSize = 9;
        public const int MAX_COUNTERS = 100;
        private static string[] counterChoices;
        private static Color[] colors;
        private bool showLabels;
        private int type = HYBRID;
        private int size = SMALL;
        private static int defaultCounter;
        private int counter;
        private int nCounters = 1;
        private short[] counters;   //for each point, 0-100 for counter (=category that can be defined by the user)
        private int[] positions;    //for each point, the stack slice, or 0 for 'show on all'
        private int[] counts = new int[MAX_COUNTERS];
        private ResultsTable rt;
        private long lastPointTime;
        private int[] counterInfo;
        private bool _promptBeforeDeleting;
        private bool promptBeforeDeletingCalled;
        private int nMarkers;
        private bool _addToOverlay;
        public static PointRoi savedPoints;

        private Pen _pen;
        private Brush _brush;

        // static
        // {
	       //  setDefaultType((int)Prefs.get(TYPE_KEY, HYBRID));
	       //  setDefaultSize((int)Prefs.get(SIZE_KEY, 1));
        // }

        public PointRoi(): this(0.0, 0.0)
        {
	        deletePoint(0);
        }

        /** Creates a new PointRoi using the specified int arrays of offscreen coordinates. */
        public PointRoi(int[] ox, int[] oy, int points):base(NumberUtil.itof(ox), NumberUtil.itof(oy), points, POINT)
		{
	        updateCounts();
        }

        /** Creates a new PointRoi using the specified float arrays of offscreen coordinates. */
        public PointRoi(float[] ox, float[] oy, int points): base(ox, oy, points, POINT)
		{
	        updateCounts();
        }

        /** Creates a new PointRoi using the specified float arrays of offscreen coordinates. */
        public PointRoi(float[] ox, float[] oy): this(ox, oy, ox.Length)
		{
        }

        /** Creates a new PointRoi using the specified coordinate arrays and options. */
        public PointRoi(float[] ox, float[] oy, string options): this(ox, oy, ox.Length)
		{
	        setOptions(options);
        }

        /** Creates a new PointRoi from a FloatPolygon. */
        public PointRoi(FloatPolygon poly): this(poly.xpoints, poly.ypoints, poly.npoints)
		{
        }

        /** Creates a new PointRoi from a Polygon. */
        public PointRoi(Polygon poly): this(NumberUtil.itof(poly.xpoints), NumberUtil.itof(poly.ypoints), poly.npoints)
		{
	        
        }

        /** Creates a new PointRoi using the specified coordinates and options. */
        public PointRoi(double ox, double oy, string options): base(makeXorYArray(ox, null, false), makeXorYArray(oy, null, true), 1, POINT)
        {
	        width = 1; height = 1;
	        incrementCounter(null);
	        setOptions(options);
        }

        /** Creates a new PointRoi using the specified offscreen int coordinates. */
        public PointRoi(int ox, int oy):base(makeXorYArray(ox, null, false), makeXorYArray(oy, null, true), 1, POINT)
        {
	        
	        width = 1; height = 1;
	        incrementCounter(null);
        }

        /** Creates a new PointRoi using the specified offscreen double coordinates. */
        public PointRoi(double ox, double oy):base(makeXorYArray(ox, null, false), makeXorYArray(oy, null, true), 1, POINT)
        {
	        width = 1; height = 1;
	        incrementCounter(null);
        }

        /** Creates a new PointRoi using the specified screen coordinates. */
        public PointRoi(int sx, int sy, ImagePlus imp):base(makeXorYArray(sx, imp, false), makeXorYArray(sy, imp, true), 1, POINT)
        {
	        //defaultCounter = 0;
	        setImage(imp);
	        width = 1; height = 1;
	        type = defaultType;
	        size = defaultSize;
	        showLabels = !Prefs.noPointLabels;
	        if (imp != null)
	        {
		        int r = 10 + size;
		        double mag = ic != null ? ic.getMagnification() : 1;
		        if (mag < 1)
			        r = (int)(r / mag);
		        imp.draw(x - r, y - r, 2 * r, 2 * r);
	        }
	        setCounter(ToolBar.getMultiPointMode() ? defaultCounter : 0);
	        incrementCounter(imp);
	        enlargeArrays(50);
	        if (Recorder.OnRecord)
	        {
				//todo:
		        // string add = Prefs.pointAddToOverlay ? " add" : "";
		        // string options = sizes[convertSizeToIndex(size)] + " " + Colors.colorToString(getColor()) + " " + types[type] + add;
		        // options = options.toLowerCase();
		        // if (Recorder.scriptMode())
			       //  Recorder.recordCall("imp.setRoi(new PointRoi(" + x + "," + y + ",\"" + options + "\"));");
		        // else
			       //  Recorder.record("makePoint", x, y, options);
	        }
        }

        public void setOptions(string options)
        {
	        if (options == null)
		        return;
	        if (options.Contains("tiny")) size = TINY;
	        else if (options.Contains("medium")) size = MEDIUM;
	        else if (options.Contains("extra")) size = EXTRA_LARGE;
	        else if (options.Contains("large")) size = LARGE;
	        else if (options.Contains("xxxl")) size = XXXL;
	        else if (options.Contains("xxl")) size = XXL;
	        if (options.Contains("cross")) type = CROSS;
	        else if (options.Contains("dot")) type = DOT;
	        else if (options.Contains("circle")) type = CIRCLE;
	        if (options.Contains("nolabel")) setShowLabels(false);
	        else if (options.Contains("label")) setShowLabels(true);
	        setStrokeColor(Colors.getColor(options, Roi.getColor()));
	        _addToOverlay = options.Contains("add");
        }

        /** Creates a one-element array with a coordinate; if 'imp' is non-null
         *  converts from a screen coordinate to an image (offscreen) coordinate.
         *  The array can be used for adding to an existing point selection */
        static float[] makeXorYArray(double value, ImagePlus imp, bool isY)
        {
	        if (imp != null)
	        {
		        ImageCanvas canvas = imp.getCanvas();
		        if (canvas != null)
		        {           //offset 0.5 converts from area to pixel center coordinates
			        value = (isY ? canvas.offScreenYD((int)value) : canvas.offScreenXD((int)value)) - 0.5;
			        if (!magnificationForSubPixel(canvas.getMagnification()))
				        value = Math.Round(value);
		        }
	        }
	        return new float[] { (float)value };
        }

        void handleMouseMove(int ox, int oy)
        {
        }

        public override void handleMouseUp(int sx, int sy)
        {
	        base.handleMouseUp(sx, sy);
	        modifyRoi(); //adds this point to previous points if shift key down
        }

        /** Draws the points on the image. */
        public override void draw(Graphics g)
        {
	        updatePolygon();
	        if (showLabels && nPoints > 1)
	        {
		        fontSize = 8;
		        double scale = size >= XXL ? 2 : 1.5;
		        fontSize += (int)(scale * convertSizeToIndex(size));
				//todo:
		        // fontSize = (int)Math.Round(fontSize);
		        //IJ.log("fontSize: "+fontSize+" "+scale);
		        font = new Font("SansSerif", fontSize, FontStyle.Regular);
		        // g.setFont(font);
		        // if (fontSize > 9)
			       //  Java2.setAntialiasedText(g, true);
	        }
	        int slice = imp != null && positions != null && imp.getStackSize() > 1 ? imp.getCurrentSlice() : 0;
	        ImageCanvas ic = imp != null ? imp.getCanvas() : null;
	        if (ic != null && overlay && ic.getShowAllList() != null && ic.getShowAllList().Contains(this) && !Prefs.showAllSliceOnly)
		        slice = 0;  // draw point irrespective of currently selected slice
	        if (Prefs.showAllPoints)
		        slice = 0;
	        //IJ.log("draw: "+positions+" "+imp.getCurrentSlice());
	        for (int i = 0; i < nPoints; i++)
	        {
		        //IJ.log(i+" "+slice+" "+(positions!=null?positions[i]:-1)+"  "+getPosition());
		        if (slice == 0 || (positions != null && (slice == positions[i] || positions[i] == 0)))
			        drawPoint(g, xp2[i], yp2[i], i + 1);
	        }
	        if (updateFullWindow)
	        {
		        updateFullWindow = false;
		        imp.draw();
	        }
			//todo:
	        // PointToolOptions.update();
	        flattenScale = 1.0;
        }

        void drawPoint(Graphics g, int x, int y, int n)
        {
	        int size2 = size / 2;
	        bool colorSet = false;
			//todo:
	        // Graphics2D g2d = (Graphics2D)g;
	        // AffineTransform saveXform = null;
	        // if (flattenScale > 1.0)
	        // {
		       //  saveXform = g2d.getTransform();
		       //  g2d.translate(x, y);
		       //  g2d.scale(flattenScale, flattenScale);
		       //  x = y = 0;
	        // }
	        Color color = strokeColor != null ? strokeColor : ROIColor;
	        if (!overlay && isActiveOverlayRoi())
	        {
		        if (color == Color.Cyan)
			        color = Color.Magenta;
		        else
			        color = Color.Cyan;
	        }
	        if (nCounters > 1 && counters != null && n <= counters.Length)
		        color = getColor(counters[n - 1]);
	        if (type == HYBRID || type == CROSS)
	        {
                if (type == HYBRID)
                {
                    _pen = new Pen(Color.White);
                }
		        else
		        {
					_pen = new Pen(color);
					colorSet = true;
		        }
		        // if (size > XXL)
			       //  g2d.setStroke(fivePixelsWide);
		        // else if (size > LARGE)
			       //  g2d.setStroke(threePixelsWide);
		        g.DrawLine(_pen, x - (size + 2), y, x + size + 2, y);
		        g.DrawLine(_pen, x, y - (size + 2), x, y + size + 2);
	        }

            if (type != CROSS && size > SMALL)
            {
				//g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			}

			if (type == HYBRID || type == DOT)
	        {
		        if (!colorSet)
                {
                    _brush = new SolidBrush(color);
			        colorSet = true;
		        }

                if (size > LARGE)
                {
                    // g2d.setStroke(onePixelWide);
				}
		        if (size > LARGE && type == DOT)
			        g.FillEllipse(_brush, x - size2, y - size2, size, size);
		        else if (size > LARGE && type == HYBRID)
			        g.FillRectangle(_brush,x - (size2 - 2), y - (size2 - 2), size - 4, size - 4);
		        else if (size > SMALL && type == HYBRID)
			        g.FillRectangle(_brush, x - (size2 - 1), y - (size2 - 1), size - 2, size - 2);
		        else
			        g.FillRectangle(_brush, x - size2, y - size2, size, size);
	        }
	        if (showLabels && nPoints > 1)
	        {
		        int xoffset = 2;
		        if (size == LARGE) xoffset = 3;
		        if (size == EXTRA_LARGE) xoffset = 4;
		        if (size == XXL) xoffset = 5;
		        if (size == XXXL) xoffset = 7;
		        int yoffset = xoffset;
		        if (size >= LARGE) yoffset = yoffset - 1;
		        if (nCounters == 1)
		        {
                    if (!colorSet)
                    {
                        _brush = new SolidBrush(color);
                        _pen = new Pen(color);
                    }
			        g.DrawString("" + n, font , _brush,x + xoffset, y + yoffset + fontSize);
		        }
		        else if (counters != null)
		        {
                    _brush = new SolidBrush(getColor(counters[n - 1]));
                    _pen = new Pen(getColor(counters[n - 1]));
			        g.DrawString("" + counters[n - 1], font , _brush,x + xoffset, y + yoffset + fontSize);
		        }
	        }
	        if ((size > TINY || type == DOT) && (type == HYBRID || type == DOT))
	        {
		        // g.setColor(Color.Black);
                _pen = new Pen(Color.Black);
		        if (size > LARGE && type == HYBRID)
			        g.DrawEllipse(_pen, x - (size2 - 1), y - (size2 - 1), size - 3, size - 3);
		        else if (size > SMALL && type == HYBRID)
			        g.DrawEllipse(_pen, x - size2, y - size2, size - 1, size - 1);
		        else
			        g.DrawEllipse(_pen, x - (size2 + 1), y - (size2 + 1), size + 1, size + 1);
	        }
	        if (type == CIRCLE)
	        {
		        // int scaledSize = (int)Math.Round(size + 1);
		        int scaledSize = size + 1;
		        // g.setColor(color);
                _pen = new Pen(color);
                if (size > LARGE)
                {
                    // g2d.setStroke(twoPixelsWide);
				}
		        g.DrawEllipse(_pen, x - scaledSize / 2, y - scaledSize / 2, scaledSize, scaledSize);
	        }
	        // if (saveXform != null)
		       //  g2d.setTransform(saveXform);
        }

        public void drawPixels(ImageProcessor ip)
        {
	        ip.setLineWidth(Analyzer.markWidth);
	        double x0 = bounds == null ? x : bounds.X;
	        double y0 = bounds == null ? y : bounds.Y;
	        for (int i = 0; i < nPoints; i++)
	        {
		        ip.moveTo((int)Math.Round(x0 + xpf[i]), (int)Math.Round(y0 + ypf[i]));
		        ip.lineTo((int)Math.Round(x0 + xpf[i]), (int)Math.Round(y0 + ypf[i]));
	        }
        }

        /** Adds a point to this PointRoi. */
        public void addPoint(ImagePlus imp, double ox, double oy)
        {
	        if (nPoints == xpf.Length)
		        enlargeArrays();
	        addPoint2(imp, ox, oy);
	        resetBoundingRect();
        }


        public void addUserPoint(ImagePlus imp, double ox, double oy)
        {
	        addPoint(imp, ox, oy);
	        nMarkers++;
        }

        private void addPoint2(ImagePlus imp, double ox, double oy)
        {
	        double xbase = getXBase();
	        double ybase = getYBase();
	        xpf[nPoints] = (float)(ox - xbase);
	        ypf[nPoints] = (float)(oy - ybase);
	        xp2[nPoints] = (int)ox;
	        yp2[nPoints] = (int)oy;
	        nPoints++;
	        incrementCounter(imp);
	        lastPointTime = DateTime.Now.Ticks;
        }

        /** Adds a point to this PointRoi. */
        public PointRoi addPoint(double x, double y)
        {
	        addPoint(null, x, y);
	        return this;
        }

        /** Adds a point at the specified stack position. */
        public void addPoint(double x, double y, int position)
        {
	        if (counters == null)
	        {
		        counters = new short[100];
		        positions = new int[100];
	        }
	        addPoint(null, x, y);
	        positions[nPoints - 1] = position;
        }

        public override void deletePoint(int index)
        {
	        base.deletePoint(index);
	        if (index >= 0 && index <= nPoints && counters != null)
	        {
		        counts[counters[index]]--;
		        for (int i = index; i < nPoints; i++)
		        {
			        counters[i] = counters[i + 1];
			        positions[i] = positions[i + 1];
		        }
				//todo:
		        // if (rt != null && WindowManager.getFrame(getCountsTitle()) != null)
			       //  displayCounts();
	        }
        }

        private void incrementCounter(ImagePlus imp)
        {
	        //IJ.log("incrementCounter: "+nPoints+" "+counter+" "+(counters!=null?""+counters.length:"null"));
	        counts[counter]++;
	        bool isStack = imp != null && imp.getStackSize() > 1;
	        if (counter != 0 || isStack || counters != null)
	        {
		        if (counters == null)
		        {
			        counters = new short[nPoints * 2];
			        positions = new int[nPoints * 2];
		        }
		        counters[nPoints - 1] = (short)counter;
		        if (imp != null)
			        positions[nPoints - 1] = imp.getStackSize() > 1 ? imp.getCurrentSlice() : 0;
		        //if (positions[nPoints-1]==0 || positions[nPoints-1]==1 || counters[nPoints-1]==0)
		        //	IJ.log("incrementCounter: "+nPoints+" "+" "+positions[nPoints-1]+" "+counters[nPoints-1]+" "+imp);
		        if (nPoints + 1 == counters.Length)
		        {
			        short[] temp = new short[counters.Length * 2];
			        Array.Copy(counters, 0, temp, 0, counters.Length);
			        counters = temp;
			        int[] temp1 = new int[counters.Length * 2];
			        Array.Copy(positions, 0, temp1, 0, positions.Length);
			        positions = temp1;
		        }
	        }
			//todo:
	        // if (rt != null && WindowManager.getFrame(getCountsTitle()) != null)
		       //  displayCounts();
        }

        /** Returns the index of the current counter. */
        public int getCounter()
        {
	        return counter;
        }

        /** Returns the count associated with the specified counter index.
         * @see #getLastCounter
         * @see <a href="http://wsr.imagej.net/macros/js/PointProperties.js">PointProperties.js</a>
         */
        public int getCount(int counter)
        {
	        if (counter == 0 && counters == null)
		        return nPoints;
	        else
		        return counts[counter];
        }

        /** Returns the index of the last counter. */
        public int getLastCounter()
        {
	        return nCounters - 1;
        }

        /** Returns the number of counters. */
        public int getNCounters()
        {
	        int n = 0;
	        for (int counter = 0; counter < nCounters; counter++)
	        {
		        if (getCount(counter) > 0) n++;
	        }
	        return n;
        }

        /** Returns the counter assocated with the specified point. */
        public int getCounter(int index)
        {
	        if (counters == null || index >= counters.Length)
		        return 0;
	        else
		        return counters[index];
        }

        public void resetCounters()
        {
	        for (int i = 0; i < counts.Length; i++)
		        counts[i] = 0;
	        counters = null;
	        positions = null;
			//todo:
	        // PointToolOptions.update();
        }

        /** Returns the points of this Roi that are not contained in the specified area ROI.
         *  Returns null if there are no resulting points or Roi is not an area roi. */
        public PointRoi subtractPoints(Roi roi)
        {
	        return checkContained(roi, false);
        }

        /** Returns the points of this Roi that are contained in the specified area ROI.
         *  Returns null if there are no resulting points or Roi is not an area roi. */
        public PointRoi containedPoints(Roi roi)
        {
	        return checkContained(roi, true);
        }

        /** Returns the points contained (not contained) in <code>roi</code> if <code>keepContained</code> is true (false). */
        PointRoi checkContained(Roi roi, bool keepContained)
        {
	        if (!roi.isArea()) return null;
	        FloatPolygon points = getFloatPolygon();
	        FloatPolygon points2 = new FloatPolygon();
	        for (int i = 0; i < points.npoints; i++)
	        {
		        if (keepContained == roi.containsPoint(points.xpoints[i], points.ypoints[i]))
			        points2.addPoint(points.xpoints[i], points.ypoints[i]);
	        }
	        if (points2.npoints == 0)
		        return null;
	        else
	        {
		        PointRoi roi2 = new PointRoi(points2.xpoints, points2.ypoints, points2.npoints);
		        roi2.copyAttributes(this);
		        return roi2;
	        }
        }

        public ImageProcessor getMask()
        {
	        ImageProcessor mask = cachedMask;
	        if (mask != null && mask.getPixels() != null)
		        return mask;
	        mask = new ByteProcessor(width, height);
	        double deltaX = getXBase() - x;
	        double deltaY = getYBase() - y;
	        for (int i = 0; i < nPoints; i++)
		        mask.putPixel((int)Math.Round(xpf[i] + deltaX), (int)Math.Round(ypf[i] + deltaY), 255);
	        cachedMask = mask;
	        return mask;
        }

        /** Returns true if (x,y) is one of the points in this collection. */
        public bool contains(int x, int y)
        {
	        for (int i = 0; i < nPoints; i++)
	        {
		        if (x == this.x + xpf[i] && y == this.y + ypf[i]) return true;
	        }
	        return false;
        }

        public void setShowLabels(bool showLabels)
        {
	        this.showLabels = showLabels;
        }

        public bool getShowLabels()
        {
	        return showLabels;
        }

        public static void setDefaultType(int type)
        {
	        if (type >= 0 && type < types.Length)
	        {
		        defaultType = type;
		        PointRoi instance = getPointRoiInstance();
		        if (instance != null)
			        instance.setPointType(defaultType);
		        Prefs.set(TYPE_KEY, type);
	        }
        }

        public static int getDefaultType()
        {
	        return defaultType;
        }

        /** Sets the point type (0=hybrid, 1=cross, 2=dot, 3=circle). */
        public void setPointType(int type)
        {
	        if (type >= 0 && type < types.Length)
		        this.type = type;
        }

        /** Returns the point type (0=hybrid, 1=cross, 2=dot, 3=circle). */
        public int getPointType()
        {
	        return type;
        }


        /** Sets the default point size, where 'size' is 0-6 (Tiny-XXXL). */
        public static void setDefaultSize(int size)
        {
	        int index = size;
	        if (index >= 0 && index < sizes.Length)
	        {
		        defaultSize = convertIndexToSize(index);
		        PointRoi instance = getPointRoiInstance();
		        if (instance != null)
			        instance.setSize(index);
		        Prefs.set(SIZE_KEY, index);
	        }
        }

        /** Returns the default point size 0-6 (Tiny-XXXL). */
        public static int getDefaultSize()
        {
	        return convertSizeToIndex(defaultSize);
        }

        /** Sets the point size, where 'size' is 0-6 (Tiny-XXXL). */
        public void setSize(int size)
        {
	        if (size >= 0 && size < sizes.Length)
		        this.size = convertIndexToSize(size);
        }

        /** Returns the current point size 0-6 (Tiny-XXXL). */
        public int getSize()
        {
	        return convertSizeToIndex(size);
        }

        private static int convertSizeToIndex(int size)
        {
	        switch (size)
	        {
		        case TINY: return 0;
		        case SMALL: return 1;
		        case MEDIUM: return 2;
		        case LARGE: return 3;
		        case EXTRA_LARGE: return 4;
		        case XXL: return 5;
		        case XXXL: return 6;
	        }
	        return 1;
        }

        private static int convertIndexToSize(int index)
        {
	        switch (index)
	        {
		        case 0: return TINY;
		        case 1: return SMALL;
		        case 2: return MEDIUM;
		        case 3: return LARGE;
		        case 4: return EXTRA_LARGE;
		        case 5: return XXL;
		        case 6: return XXXL;
	        }
	        return SMALL;
        }

        /** Always returns true. */
        public bool subPixelResolution()
        {
	        return true;
        }

        private static PointRoi getPointRoiInstance()
        {
	        ImagePlus imp = WindowManager.getCurrentImage();
	        if (imp != null)
	        {
		        Roi roi = imp.getRoi();
		        if (roi != null)
		        {
			        if (roi is PointRoi)
				        return (PointRoi)roi;
		        }
	        }
	        return null;
        }

        public void setCounter(int counter)
        {
	        this.counter = counter;
	        if (counter > nCounters - 1 && nCounters < MAX_COUNTERS)
		        nCounters = counter + 1;
        }

        public bool promptBeforeDeleting()
        {
	        if (promptBeforeDeletingCalled && getNCounters() == 1)
		        return _promptBeforeDeleting;
	        else
		        return (nMarkers > 8 || getNCounters() > 1) && imp != null && imp.getWindow() != null;
        }

        public void promptBeforeDeleting(Boolean prompt)
        {
            _promptBeforeDeleting = prompt;
	        promptBeforeDeletingCalled = true;
        }

        public static void setDefaultCounter(int counter)
        {
	        defaultCounter = counter;
        }

        /** Returns an array containing for each point:
         *  The counter number (0-100) in the lower 8 bits, and the slice number
         *  (or 0, if the point appears on all slices) in the higher 24 bits.
         *  Used when writing a Roi to file (RoiEncoder) */
        public int[] getCounters()
        {
	        if (nPoints > 65535)
		        return null;
	        int[] temp = new int[nPoints];
	        if (counters != null)
	        {
		        for (int i = 0; i < nPoints; i++)
			        temp[i] = (counters[i] & 0xff) + (positions[i] << 8);
	        }
	        return temp;
        }

        /** Sets the counter number and slice number for each point from an
         *  array, where the lower 8 bits are the counter number and the
         *  higher 24 bits contain the slice position of each point.
         *  Used when reading a roi fromfile (RoiDecoder).  */
        public void setCounters(int[] counters)
        {
	        if (counters != null)
	        {
		        int n = counters.Length;
		        this.counters = new short[n * 2];
		        this.positions = new int[n * 2];
		        for (int i = 0; i < n; i++)
		        {
			        int counter = counters[i] & 0xff;
			        int position = counters[i] >> 8;
			        //IJ.log(i+" cnt="+counter+" slice="+position);
			        this.counters[i] = (short)counter;
			        this.positions[i] = position;
			        if (counter < counts.Length && counter > nCounters - 1)
				        nCounters = counter + 1;
		        }
		        updateCounts();
	        }
        }

        /** Updates the counts for each category in 'counters' */
        public void updateCounts()
        {
	        Array.Fill(counts, 0);
	        for (int i = 0; i < nPoints; i++)
		        counts[(counters == null || counters[i] >= counts.Length) ? 0 : counters[i]]++;
        }

        /** Returns the stack slice of the point with the given index, or 0 if no slice defined for this point */
        public int getPointPosition(int index)
        {
	        if (positions != null && index < nPoints)
		        return positions[index];
	        else
		        return 0;
        }

        public void displayCounts()
        {
	        ImagePlus imp = getImage();
	        String firstColumnHdr = "Slice";
	        rt = new ResultsTable();
	        int row = 0;
	        if (imp != null && imp.getStackSize() > 1 && positions != null)
	        {
		        int nChannels = 1;
		        int nSlices = 1;
		        int nFrames = 1;
		        bool isHyperstack = true;
		        if (imp.isComposite() || imp.isHyperStack())
		        {
			        nChannels = imp.getNChannels();
			        nSlices = imp.getNSlices();
			        nFrames = imp.getNFrames();
			        int nDimensions = 2;
			        if (nChannels > 1) nDimensions++;
			        if (nSlices > 1) nDimensions++;
			        if (nFrames > 1) nDimensions++;
			        if (nDimensions == 3)
			        {
				        isHyperstack = false;
				        if (nChannels > 1)
					        firstColumnHdr = "Channel";
			        }
			        else
				        firstColumnHdr = "Image";
		        }
		        int firstSlice = int.MaxValue;
		        for (int i = 0; i < nPoints; i++)
		        {
			        if (positions[i] > 0 && positions[i] < firstSlice)
				        firstSlice = positions[i];
		        }
		        if (firstSlice == int.MaxValue)
			        firstSlice = 0;
		        int lastSlice = 0;
		        if (firstSlice > 0)
		        {
			        for (int i = 0; i < nPoints; i++)
			        {
				        if (positions[i] > lastSlice)
					        lastSlice = positions[i];
			        }
		        }
		        if (firstSlice > 0)
		        {
			        for (int slice = firstSlice; slice <= lastSlice; slice++)
			        {
				        rt.setValue(firstColumnHdr, row, slice);
				        if (isHyperstack)
				        {
					        int[] position = imp.convertIndexToPosition(slice);
					        if (nChannels > 1)
						        rt.setValue("Channel", row, position[0]);
					        if (nSlices > 1)
						        rt.setValue("Slice", row, position[1]);
					        if (nFrames > 1)
						        rt.setValue("Frame", row, position[2]);
				        }
				        for (int counter = 0; counter < nCounters; counter++)
				        {
					        int count = 0;
					        for (int i = 0; i < nPoints; i++)
					        {
						        if (slice == positions[i] && counter == counters[i])
							        count++;
					        }
					        rt.setValue("Ctr " + counter, row, count);
				        }
				        row++;
			        }
		        }
	        }
	        rt.setValue(firstColumnHdr, row, "Total");
	        for (int i = 0; i < nCounters; i++)
		        rt.setValue("Ctr " + i, row, counts[i]);
	        rt.show(getCountsTitle());
	        if (IJ.debugMode) debug();
        }

        private void debug()
        {
	        FloatPolygon p = getFloatPolygon();
	        ResultsTable rt = new ResultsTable();
	        for (int i = 0; i < nPoints; i++)
	        {
		        if (counters != null)
		        {
			        rt.setValue("Counter", i, counters[i]);
			        rt.setValue("Position", i, positions[i]);
		        }
		        rt.setValue("X", i, p.xpoints[i]);
		        rt.setValue("Y", i, p.ypoints[i]);
	        }
	        rt.show(getCountsTitle());
        }

        private string getCountsTitle()
        {
	        return "Counts_" + (imp != null ? imp.getTitle() : "");
        }

        public static string[] getCounterChoices()
        {
	        if (counterChoices == null)
	        {
		        counterChoices = new string[MAX_COUNTERS];
		        for (int i = 0; i < MAX_COUNTERS; i++)
			        counterChoices[i] = "" + i;
	        }
	        return counterChoices;
        }

        private static Color getColor(int index)
        {
	        if (colors == null)
	        {
		        colors = new Color[MAX_COUNTERS];
		        colors[0] = Color.Yellow; colors[1] = Color.Magenta; colors[2] = Color.Cyan;
		        colors[3] = Color.Orange; colors[4] = Color.Green; colors[5] = Color.Blue;
		        colors[6] = Color.White; colors[7] = Color.DarkGray; colors[8] = Color.Pink;
		        colors[9] = Color.LightGray;
	        }
	        if (colors[index] != null)
		        return colors[index];
	        else
	        {
		        Random ran = new Random();
		        float r = (float)ran.NextDouble();
		        float g = (float)ran.NextDouble();
		        float b = (float)ran.NextDouble();
                Color c = Color.FromArgb((byte)r, (byte)g, (byte)b);
		        colors[index] = c;
		        return c;
	        }
        }

        /** Returns a point index if it has been at least one second since
	        the last point was added and the specified screen coordinates are
	        inside or near a point, otherwise returns -1. */
        public int isHandle(int sx, int sy)
        {
	        if ((DateTime.Now.Ticks- lastPointTime) < 1000L)
		        return -1;
	        int size = HANDLE_SIZE + this.size;
	        int halfSize = size / 2;
	        int handle = -1;
	        int sx2, sy2;
	        int slice = !Prefs.showAllPoints && positions != null && imp != null && imp.getStackSize() > 1 ? imp.getCurrentSlice() : 0;
	        for (int i = 0; i < nPoints; i++)
	        {
		        if (slice != 0 && slice != positions[i])
			        continue;
		        sx2 = xp2[i] - halfSize; sy2 = yp2[i] - halfSize;
		        if (sx >= sx2 && sx <= sx2 + size && sy >= sy2 && sy <= sy2 + size)
		        {
			        handle = i;
			        break;
		        }
	        }
	        return handle;
        }

        /** Returns the points as an array of Points.
         * Wilhelm Burger: modified to use FloatPolygon for correct point positions.
        */
        public Point[] getContainedPoints()
        {
	        FloatPolygon p = getFloatPolygon();
	        Point[] points = new Point[p.npoints];
	        for (int i = 0; i < p.npoints; i++)
		        points[i] = new Point((int)Math.Round(p.xpoints[i] - 0.5f), (int)Math.Round(p.ypoints[i] - 0.5f));
	        return points;
        }

        /** Returns the points as a FloatPolygon. */
        public FloatPolygon getContainedFloatPoints()
        {
	        return getFloatPolygon();
        }

        /**
         * Custom iterator for points contained in a {@link PointRoi}.
         * Author: W. Burger
        */
       //  public IEnumerator<Point> iterator()
       //  {
	      //   return new IEnumerator<Point>() 
       //      {
			    // Point[] pnts = getContainedPoints();
	      //       final int n = pnts.length;
	      //       int next = (n == 0) ? 1 : 0;
	      //       @Override
			    // public boolean hasNext()
	      //       {
		     //        return next < n;
	      //       }
	      //       @Override
			    // public Point next()
	      //       {
		     //        if (next >= n)
		     //        {
			    //         throw new NoSuchElementException();
		     //        }
		     //        Point pnt = pnts[next];
		     //        next = next + 1;
		     //        return pnt;
	      //       }
	      //       @Override
			    // public void remove()
	      //       {
		     //        throw new NotSupportedException();
	      //       }
       //      };
       //  }


        protected override int getClosestPoint(double x, double y, FloatPolygon points)
        {
	        int index = -1;
	        double distance = double.MaxValue;
	        int slice = imp != null && positions != null && imp.getStackSize() > 1 ? imp.getCurrentSlice() : 0;
	        if (Prefs.showAllPoints)
		        slice = 0;
	        for (int i = 0; i < points.npoints; i++)
	        {
		        double dx = points.xpoints[i] - x;
		        double dy = points.ypoints[i] - y;
		        double distance2 = dx * dx + dy * dy;
		        if (distance2 < distance && (slice == 0 || slice == positions[i]))
		        {
			        distance = distance2;
			        index = i;
		        }
	        }
	        return index;
        }

        /** Returns a copy of this PointRoi. */
        public object clone() {
	        PointRoi r = (PointRoi)base.clone();
	        if (counters != null)
	        {
		        r.counters = new short[counters.Length];
		        for (int i = 0; i < counters.Length; i++)
			        r.counters[i] = counters[i];
	        }
	        if (positions != null)
	        {
		        r.positions = new int[positions.Length];
		        for (int i = 0; i < positions.Length; i++)
			        r.positions[i] = positions[i];
	        }
	        if (counts != null)
	        {
		        r.counts = new int[counts.Length];
		        for (int i = 0; i < counts.Length; i++)
			        r.counts[i] = counts[i];
	        }
	        return r;
        }

        /* Returns a version of this PointRoi that only contains points inside 'roi'. */
        public PointRoi crop(Roi roi)
        {
	        PointRoi points = (PointRoi)clone();
	        Polygon p = points.getPolygon();
	        for (int i = points.size() - 1; i >= 0; i--)
	        {
		        if (!roi.contains(p.xpoints[i], p.ypoints[i]))
		        {
			        points.deletePoint(i);
		        }
	        }
	        return points;
        }

	    public override void copyAttributes(Roi roi2)
        {
	        base.copyAttributes(roi2);
	        if (roi2 is PointRoi) {
	            PointRoi p2 = (PointRoi)roi2;
	            this.type = p2.type;
	            this.size = p2.size;
	            this.showLabels = p2.showLabels;
	            this.fontSize = p2.fontSize;
            }
	    }

        public void setCounterInfo(int[] info)
        {
	        counterInfo = info;
        }

        public int[] getCounterInfo()
        {
	        return counterInfo;
        }

        public bool addToOverlay()
        {
	        return _addToOverlay;
        }

        public string toString()
        {
	        if (nPoints > 1)
		        return ("Roi[Points, count=" + nPoints + "]");
	        else
		        return ("Roi[Point, x=" + x + ", y=" + y + "]");
        }

        /** @deprecated */
        public void setHideLabels(bool hideLabels)
        {
	        this.showLabels = !hideLabels;
        }

        /** @deprecated */
        public static void setDefaultMarkerSize(String size)
        {
        }

        /** @deprecated */
        public static String getDefaultMarkerSize()
        {
	        return sizes[defaultSize];
        }

        /** Deprecated */
        public static void setDefaultCrossColor(Color color)
        {
        }

        /** Deprecated */
        public static Color getDefaultCrossColor()
        {
	        return Color.Empty;
        }

}

}
